define([
    'geoutils',
    'tileutils',
    'coordUtils',
], function (geoutils, tileutils, coordUtils) {

    var $elev = {};

//======================================================================================================================
//region pixel and buffer manipulation
//======================================================================================================================

    function getPixels(url, type, cb) {
        if (!cb) {
            cb = type;
            type = '';
        }
        var ext = type;
        if (!ext && url.indexOf('.') > -1) {
            ext = url.substring(url.lastIndexOf('.'));
        }
        ext = ext.toUpperCase();
        switch (ext) {
            case '.GIF':
                break;
            default:
                defaultImage(url, cb);
                break;
        }
    }

    $elev.getPixels = getPixels;

    var context = null;
    if (document) {
        var canvas = document.createElement('canvas')
        canvas.width = geoutils.tileImgSize || 512;
        canvas.height = geoutils.tileImgSize || 512;
        context = canvas.getContext('2d');
    }

    function defaultImage(url, cb) {
        if (!context) return;
        var img = new Image();
        img.crossOrigin = "Anonymous";
        img.onload = function () {
            context.drawImage(img, 0, 0);
            var pixels = context.getImageData(0, 0, img.width, img.height);
            // RGBA array
            cb(null, {
                data: new Uint8Array(pixels.data.buffer),
                offset: 0,
                shape: [img.width, img.height, 4],
                stride: [4, 4 * img.width, 1],
                order: [2, 0, 1],
                size: img.width * img.height * 4,
            });
        }
        img.onerror = function (err) {
            cb(err);
        };
        img.src = url;
    }

    $elev.getPixels = getPixels;

//======================================================================================================================
//endregion
//======================================================================================================================

//======================================================================================================================
//region tile set
//======================================================================================================================

    function mix(min, max, p) {
        return min * (1 - p) + max * p;
    }

    function clamp(min, max, v) {
        return Math.max(min, Math.min(max, v));
    }

    function step(min, max, v) {
        return (clamp(min, max, v) - min) / (max - min);
    }

    $elev.TileSet = function (urlBase) {

        var self = this;

        // 10s ttl at most
        this.defaultTtl = 10000;

        this.tiles = {};

        this.freeAll = function () {
            this.tiles = {};
        };

        this.maintainTtl = function () {
            var scanAt = Date.now();
            Object.keys(self.tiles).forEach(function (z) {
                var levelTiles = self.tiles[z];

                Object.keys(levelTiles).forEach(function (x) {
                    var count = 0;
                    var ys = levelTiles[x];
                    var newYs = {};
                    Object.keys(ys).forEach(function (y) {
                        var tile = ys[y];
                        if (tile && tile.validBefore < scanAt) {
                            ys[y] = undefined;
                        } else if (tile) {
                            newYs[y] = tile;
                            count++;
                        }
                    });
                    if (count <= 0) {
                        delete levelTiles[x];
                    } else {
                        levelTiles[x] = newYs;
                    }
                });
            });
        };

        setInterval(function () {
            self.maintainTtl();
        }, 2000);

        this._getCacheTileUrl = function (server, coords) {
            var z = coords[0], x = coords[1], y = coords[2];
            var cacheServer = 'https://edn1.sagnitude.com/tileServer/cache/';
            var args = '?cors=0';
            var tile = z + '/' + x + '/' + y + '_' + server;
            return cacheServer + tile + args;
        };

        this.assembleRgbUrl = function (coords) {
            if (geoutils.tileImgSize == 256) {
                return this._getCacheTileUrl('mbrgb256', coords);
            } else {
                return this._getCacheTileUrl('mbrgb512', coords);
            }
        };

        this._getElevationData = function (z, x, y, callback) {
            if (!self.tiles[z]) self.tiles[z] = {};
            if (!self.tiles[z][x]) self.tiles[z][x] = {};
            if (!self.tiles[z][x][y]) self.tiles[z][x][y] = {};
            var tile = self.tiles[z][x][y];
            if (!tile.onLoad) {
                tile.onLoad = [];
            }
            if (tile.data) {
                tile.validBefore = Date.now() + self.defaultTtl;
                return callback && callback(tile.data);
            } else if (tile.loading) {
                tile.onLoad.push(callback);
                return;
            }

            tile.loading = true;
            tile.onLoad.push(callback);

            var url = self.assembleRgbUrl([z, x, y]);

            getPixels(url, null, function (err, pixels) {
                if (err) pixels = null;
                // var parserIndex = 2 * (coords[1] % 2) + coords[2] % 2;
                // var args = [pixels, coords, tiles, parserIndex, geoutils.tileImgSize];

                var elevations = [];

                if (pixels) {
                    //colors => elevations
                    for (var e = 0; e < pixels.data.length; e += 4) {
                        var R = pixels.data[e];
                        var G = pixels.data[e + 1];
                        var B = pixels.data[e + 2];
                        elevations.push(-10000 + ((R * 256 * 256 + G * 256 + B) * 0.1));
                    }
                    var blurElevations = new Float32Array(pixels.data.length / 4);
                    elevations = geoutils.applyElevationBlur(elevations, geoutils.tileImgSize, blurElevations);
                } else {
                    elevations = new Float32Array(geoutils.tileImgSize * geoutils.tileImgSize * 4).fill(0);
                }

                tile.data = elevations;
                tile.createdAt = Date.now();
                tile.validBefore = tile.createdAt + self.defaultTtl;

                var callbacks = tile.onLoad || [];
                tile.onLoad = [];
                callbacks.forEach(function (cb) {
                    cb && cb(tile.data);
                });
            });
        };

        this.readElevationDataAtWgs = function (lng, lat, zoom, callback) {
            // resolve zxy from lng,lat,zoom
            var tile = tileutils.lngLatToTile(lng, lat, zoom);
            var x = tile[0];
            var y = tile[1];
            var tileImgSize = geoutils.tileImgSize;

            // minX, maxY
            var tile0 = tileutils.tile2lngLat(x, y, 0, 0, tileImgSize, zoom);
            // maxX, minY
            var tile1 = tileutils.tile2lngLat(x, y, tileImgSize, tileImgSize, tileImgSize, zoom);
            var pixelX = step(tile0[0], tile1[0], lng) * tileImgSize;
            var pixelY = (1 - step(tile1[1], tile0[1], lat)) * tileImgSize;

            self._getElevationData(zoom, x, y, function (data) {
                var tileImgSize = geoutils.tileImgSize;

                // NearestFilter
                var elevation = data[(~~pixelX) + (~~pixelY) * tileImgSize];

                callback && callback(elevation);
            });
        };

        this.readElevationDataAtGcj = function (lng, lat, zoom, callback) {
            var gcj = coordUtils.gcj02_to_wgs84(lng, lat);
            return self.readElevationDataAtWgs(gcj.lng, gcj.lat, zoom, callback);
        };

    };


    $elev.altitudeTileSet = new $elev.TileSet();

    $elev.altitudeQueryZoom = 12;

//======================================================================================================================
//endregion
//======================================================================================================================

    return $elev;

});